#ifndef BOOST_LEAF_RESULT_HPP_INCLUDED
#define BOOST_LEAF_RESULT_HPP_INCLUDED

// Copyright (c) 2018-2020 Emil Dotchevski and Reverge Studios, Inc.

// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_LEAF_ENABLE_WARNINGS
#   if defined(__clang__)
#       pragma clang system_header
#   elif (__GNUC__*100+__GNUC_MINOR__>301)
#       pragma GCC system_header
#   elif defined(_MSC_VER)
#       pragma warning(push,1)
#   endif
#endif

#include <boost/leaf/error.hpp>
#include <climits>

namespace boost { namespace leaf {

    class bad_result:
        public std::exception,
        public error_id
    {
        char const * what() const noexcept final override
        {
            return "boost::leaf::bad_result";
        }

    public:

        explicit bad_result( error_id id ) noexcept:
            error_id(id)
        {
            BOOST_LEAF_ASSERT(value());
        }
    };

    ////////////////////////////////////////

    namespace leaf_detail
    {
        template <class T>
        struct stored
        {
            using type = T;
            using value_type = T;
            using value_type_const = T const;
            using value_cref = T const &;
            using value_ref = T &;
            using value_rv_cref = T const &&;
            using value_rv_ref = T &&;
        };

        template <class T>
        struct stored<T &>
        {
            using type = std::reference_wrapper<T>;
            using value_type_const = T;
            using value_type = T;
            using value_ref = T &;
            using value_cref = T &;
            using value_rv_ref = T &;
            using value_rv_cref = T &;
        };

        class result_discriminant
        {
            unsigned state_;

        public:

            enum kind_t
            {
                no_error = 0,
                err_id = 1,
                ctx_ptr = 2,
                val = 3
            };

            explicit result_discriminant( error_id id ) noexcept:
                state_(id.value())
            {
                BOOST_LEAF_ASSERT(state_==0 || (state_&3)==1);
            }

            struct kind_val { };
            explicit result_discriminant( kind_val ) noexcept:
                state_(val)
            {
            }

            struct kind_ctx_ptr { };
            explicit result_discriminant( kind_ctx_ptr ) noexcept:
                state_(ctx_ptr)
            {
            }

            kind_t kind() const noexcept
            {
                return kind_t(state_&3);
            }

            error_id get_error_id() const noexcept
            {
                BOOST_LEAF_ASSERT(kind()==no_error || kind()==err_id);
                return make_error_id(state_);
            }
        };
    }

    ////////////////////////////////////////

    template <class T>
    class result
    {
        template <class U>
        friend class result;

        using result_discriminant = leaf_detail::result_discriminant;

        struct error_result
        {
            error_result( error_result && ) = default;
            error_result( error_result const & ) = delete;
            error_result & operator=( error_result const & ) = delete;

            result & r_;

            error_result( result & r ) noexcept:
                r_(r)
            {
            }

            template <class U>
            operator result<U>() noexcept
            {
                switch(r_.what_.kind())
                {
                case result_discriminant::val:
                    return result<U>(error_id());
                case result_discriminant::ctx_ptr:
                    return result<U>(std::move(r_.ctx_));
                default:
                    return result<U>(std::move(r_.what_));
                }
            }

            operator error_id() noexcept
            {
                switch(r_.what_.kind())
                {
                case result_discriminant::val:
                    return error_id();
                case result_discriminant::ctx_ptr:
                {
                    error_id captured_id = r_.ctx_->propagate_captured_errors();
                    leaf_detail::id_factory<>::current_id = captured_id.value();
                    return captured_id;
                }
                default:
                    return r_.what_.get_error_id();
                }
            }
        };

        using stored_type = typename leaf_detail::stored<T>::type;
        using value_type = typename leaf_detail::stored<T>::value_type;
        using value_type_const = typename leaf_detail::stored<T>::value_type_const;
        using value_ref = typename leaf_detail::stored<T>::value_ref;
        using value_cref = typename leaf_detail::stored<T>::value_cref;
        using value_rv_ref = typename leaf_detail::stored<T>::value_rv_ref;
        using value_rv_cref = typename leaf_detail::stored<T>::value_rv_cref;

        union
        {
            stored_type stored_;
            context_ptr ctx_;
        };

        result_discriminant what_;

        void destroy() const noexcept
        {
            switch(this->what_.kind())
            {
            case result_discriminant::val:
                stored_.~stored_type();
                break;
            case result_discriminant::ctx_ptr:
                BOOST_LEAF_ASSERT(!ctx_ || ctx_->captured_id_);
                ctx_.~context_ptr();
            default:
                break;
            }
        }

        template <class U>
        result_discriminant move_from( result<U> && x ) noexcept
        {
            auto x_what = x.what_;
            switch(x_what.kind())
            {
            case result_discriminant::val:
                (void) new(&stored_) stored_type(std::move(x.stored_));
                break;
            case result_discriminant::ctx_ptr:
                BOOST_LEAF_ASSERT(!x.ctx_ || x.ctx_->captured_id_);
                (void) new(&ctx_) context_ptr(std::move(x.ctx_));
            default:
                break;
            }
            return x_what;
        }

        result( result_discriminant && what ) noexcept:
            what_(std::move(what))
        {
            BOOST_LEAF_ASSERT(what_.kind()==result_discriminant::err_id || what_.kind()==result_discriminant::no_error);
        }

        error_id get_error_id() const noexcept
        {
            BOOST_LEAF_ASSERT(what_.kind()!=result_discriminant::val);
            return what_.kind()==result_discriminant::ctx_ptr ? ctx_->captured_id_ : what_.get_error_id();
        }

        static int init_T_with_U( T && );

    protected:

        void enforce_value_state() const
        {
            if( what_.kind() != result_discriminant::val )
                ::boost::leaf::throw_exception(bad_result(get_error_id()));
        }

    public:

        result( result && x ) noexcept:
            what_(move_from(std::move(x)))
        {
        }

        template <class U>
        result( result<U> && x ) noexcept:
            what_(move_from(std::move(x)))

        {
        }

        result():
            stored_(stored_type()),
            what_(result_discriminant::kind_val{})
        {
        }

        result( value_type && v ) noexcept:
            stored_(std::forward<value_type>(v)),
            what_(result_discriminant::kind_val{})
        {
        }

        result( value_type const & v ):
            stored_(v),
            what_(result_discriminant::kind_val{})
        {
        }

        result( error_id err ) noexcept:
            what_(err)
        {
        }

        // SFINAE: T can be initialized with a U, e.g. result<std::string>("literal").
        // Not using is_constructible on purpose, bug with COMPILER=/usr/bin/clang++ CXXSTD=11 clang 3.3.
        template <class U>
        result( U && u, decltype(init_T_with_U(std::forward<U>(u))) * = 0 ):
            stored_(std::forward<U>(u)),
            what_(result_discriminant::kind_val{})
        {
        }

        result( std::error_code const & ec ) noexcept:
            what_(error_id(ec))
        {
        }

        template <class Enum>
        result( Enum e, typename std::enable_if<std::is_error_code_enum<Enum>::value, int>::type * = 0 ) noexcept:
            what_(error_id(e))
        {
        }

        result( context_ptr && ctx ) noexcept:
            ctx_(std::move(ctx)),
            what_(result_discriminant::kind_ctx_ptr{})
        {
        }

        ~result() noexcept
        {
            destroy();
        }

        result & operator=( result && x ) noexcept
        {
            destroy();
            what_ = move_from(std::move(x));
            return *this;
        }

        template <class U>
        result & operator=( result<U> && x ) noexcept
        {
            destroy();
            what_ = move_from(std::move(x));
            return *this;
        }

        explicit operator bool() const noexcept
        {
            return what_.kind() == result_discriminant::val;
        }

        value_cref value() const &
        {
            enforce_value_state();
            return stored_;
        }

        value_ref value() &
        {
            enforce_value_state();
            return stored_;
        }

        value_rv_cref value() const &&
        {
            enforce_value_state();
            return std::move(stored_);
        }

        value_rv_ref value() &&
        {
            enforce_value_state();
            return std::move(stored_);
        }

        value_cref operator*() const &
        {
            return value();
        }

        value_ref operator*() &
        {
            return value();
        }

        value_rv_cref operator*() const &&
        {
            return value();
        }

        value_rv_ref operator*() &&
        {
            return value();
        }

        value_type_const * operator->() const
        {
            return &value();
        }

        value_type * operator->()
        {
            return &value();
        }

        error_result error() noexcept
        {
            return error_result{*this};
        }

        template <class... Item>
        error_id load( Item && ... item ) noexcept
        {
            return error_id(error()).load(std::forward<Item>(item)...);
        }
    };

    ////////////////////////////////////////

    namespace leaf_detail
    {
        struct void_ { };
    }

    template <>
    class result<void>:
        result<leaf_detail::void_>
    {
        using result_discriminant = leaf_detail::result_discriminant;
        using void_ = leaf_detail::void_;
        using base = result<void_>;

        template <class U>
        friend class result;

        result( result_discriminant && what ) noexcept:
            base(std::move(what))
        {
        }

    public:

        using value_type = void;

        result( result && x ) noexcept:
            base(std::move(x))
        {
        }

        result() noexcept
        {
        }

        result( error_id err ) noexcept:
            base(err)
        {
        }

        result( std::error_code const & ec ) noexcept:
            base(ec)
        {
        }

        template <class Enum>
        result( Enum e, typename std::enable_if<std::is_error_code_enum<Enum>::value, Enum>::type * = 0 ) noexcept:
            base(e)
        {
        }

        result( context_ptr && ctx ) noexcept:
            base(std::move(ctx))
        {
        }

        ~result() noexcept
        {
        }

        void value() const
        {
            base::enforce_value_state();
        }

        using base::operator=;
        using base::operator bool;
        using base::get_error_id;
        using base::error;
        using base::load;
    };

    ////////////////////////////////////////

    template <class R>
    struct is_result_type;

    template <class T>
    struct is_result_type<result<T>>: std::true_type
    {
    };

} }

#endif
